#include "Tracer.h"
#include <cstdlib>
#include <time.h>


using namespace std;

Tracer::Tracer(){
   scene = new Scene();
   scene->setup();
}

Tracer::~Tracer(){
    delete scene;
}

Vector3<float> Tracer::traceRay(Ray &r, float depth, int iter){

    //Get random variable for russian roulette ray termination
    float stop = (float)rand()/RAND_MAX;


    Vector3<float> geoColor(0.0);
    float firstHit = 1000;
    Vector3<float> hitPoint, lightVec, newDir;
    Vector3<float> hitNormal;
    Geometry *firstHitGeo;

    //Find if ray intersects and object in the scene
    for(unsigned int i = 0; i< scene->sceneGeometry.size(); i++){
        Geometry *g = scene->sceneGeometry.at(i);
        float hit = g->intersect(r);
        if(hit > 0 && hit < firstHit){
            firstHit = hit;
            geoColor = g->material->getColor();
            firstHitGeo = g;
        }
    }

    //If we didn't hit anything, return black
    if(firstHit >= 1000){
        Vector3<float> ret(0.0);
        return ret;
    }

    //If hit object is light, return light color
    if(firstHitGeo->material->isLight()){
        return firstHitGeo->material->getColor();
    }

    //Stop recursion when depth value is below stop value or max iterations reached
    if(depth < stop || iter > 20){
        return firstHitGeo->material->getColor();
    }


    //If we hit something, get geometry properties at hitpoint
    if(firstHit < 1000 && firstHit > 0.0001){
        float refl = firstHitGeo->material->getReflection();
        float refr = firstHitGeo->material->getRefraction();


        hitPoint = r.origin + r.direction * firstHit;
        hitNormal = firstHitGeo->getNormal(hitPoint);


        //Get direct contribution
        Vector3<float> directColor(0.0);
        directColor = getDirect(r.direction, hitPoint, hitNormal, geoColor, firstHitGeo->material->getReflection());

        //Create next ray
        Ray newRay(hitPoint, hitNormal);


        //If the material is perfectly specular: get reflected ray
        if(refl == 1.0){
            newDir = getSpecularRay(r.direction, hitPoint, hitNormal);
        }

        //If material refracts
        else if(refr == 1.0){
            newDir = getRefractedRay(r.direction, firstHitGeo, hitNormal);
        }

        //If material is not perfectly specular or refractive, get diffuse ray
        else{
            newDir = getDiffuseRay(r.direction, refl, hitNormal);
        }

        //Set new ray direction
        newRay.direction = newDir;

        //Recursive call, depth is multiplied with reflective property to get more rays in specular areas
        return (0.9*directColor + 0.1*traceRay(newRay, depth*refl, iter+1));

    }


    //Not supposed to happen, but if some case is not handled, return color of hit object
    return geoColor;
}


//Get randomized direction for diffuse ray
Vector3<float> Tracer::getDiffuseRay(Vector3<float> rayDir, float refl, Vector3<float> hitNormal){
//    hitNormal.Normalize();
    Vector3<float> t1 = rayDir - ((rayDir * hitNormal) * hitNormal);
    t1.Normalize();
    Vector3<float> t2 = Cross(t1, hitNormal);

    //Get uniformly distributed random variables
    float a = (float)rand()/RAND_MAX;
    float b1 = (float)rand()/RAND_MAX * 2.0 - 1.0;
    float b2 = (float)rand()/RAND_MAX * 2.0 - 1.0;

    Vector3<float> newDir;
    newDir = (a * hitNormal) + (b1  *(refl)* t1) + (b2 *(refl)* t2);
    return Vector3<float>(newDir[0],newDir[1],newDir[2]);
}

//Get direction for specular ray
Vector3<float> Tracer::getSpecularRay(Vector3<float> rayDir, Vector3<float> hitPoint, Vector3<float> hitNormal){
    //hitNormal.Normalize();
    rayDir.Normalize();
    Vector3<float> newDir = rayDir - (2.0f * (rayDir*hitNormal) * hitNormal);
    return Vector3<float>(newDir[0],newDir[1],newDir[2]);
}

//TODO:Get direction for refracted ray
Vector3<float> Tracer::getRefractedRay(Vector3<float> rayDir, Geometry *geo, Vector3<float> hitNormal){
    Vector3<float> newDir(0.0);
    return newDir;
}

//Get direct light contribution from each light
Vector3<float> Tracer::getDirect(Vector3<float> rayDir, Vector3<float> hitPoint, Vector3<float> hitNormal, Vector3<float> geoColor, float geoSpec){
    float direct[3];
    direct[0] = 0.0;
    direct[1] = 0.0;
    direct[2] = 0.0;


    for(unsigned int j = 0; j< scene->sceneGeometry.size(); j++){
        if(scene->sceneGeometry.at(j)->material->isLight()){
            Sphere *light = (Sphere*)scene->sceneGeometry.at(j);
            Vector3<float> lightVec = light->center - hitPoint;

            //Find if hitpoint is in shadow
            float shade = calcShade(hitPoint, lightVec);

            lightVec.Normalize();
            Vector3<float> lightcolor = light->material->getColor();
            Vector3<float> speccolor(1.0, 1.0, 1.0);
            Vector3<float> r = lightVec - 2.0f * (lightVec * hitNormal) * hitNormal;
            rayDir.Normalize();

            //Add color in proportion to lightsource direction
            float specdot = 0.1;

            specdot = rayDir * r;
            specdot = pow(specdot, 50) * (geoSpec+0.0001);

            float dot = lightVec * hitNormal;

            if(dot > 0.0001){
                direct[0] += (geoColor[0] * dot * shade * lightcolor[0] + specdot);
                direct[1] += (geoColor[1] * dot * shade * lightcolor[1] + specdot);
                direct[2] += (geoColor[2] * dot * shade * lightcolor[2] + specdot);
            }
        }
    }
    return Vector3<float>(direct[0],direct[1],direct[2]);
;
}

//Find if hit object is in shade
float Tracer::calcShade(Vector3<float> intersectPoint, Vector3<float> lightDir){
    float shade = 1.0;
    for(unsigned int i = 0; i< scene->sceneGeometry.size(); i++){
        float hitDistance = 1000;
        Geometry *tmp_geo = scene->sceneGeometry.at(i);
        Ray shadowRay(intersectPoint, lightDir);
        hitDistance = tmp_geo->intersect(shadowRay);
        Vector3<float> shadowHit = shadowRay.origin + shadowRay.direction * hitDistance;

        //If any object is between hit object and light, it is in shade
        if(hitDistance < 1000 && hitDistance > 0.0001 && shadowHit.Length() < lightDir.Length() && !tmp_geo->material->isLight()){
            shade = 0.0;
            break;
        }

    }
    return shade;
}


